package link.luyu.plugin.brochain;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.web3j.protocol.core.methods.response.AbiDefinition;

@SuppressWarnings("unchecked")
public class Config {

    private String defaultChainPath;

    private String chainPath;
    private String regionId;
    private Set<String> consensusNodes;

    private String nodeUrl;

    private Map<String, Resource> resources;

    public Config(Map<String, Object> map) {
        this.defaultChainPath = (String) map.get("chainPath");

        if (map.containsKey("chain")) {
            Map<String, Object> chain = (Map<String, Object>) map.get("chain");
            this.chainPath = (String) chain.get("chainPath");
            this.regionId = (String) chain.get("regionId");
            List<String> consensusNodeList = (List<String>) chain.get("consensusNodes");
            this.consensusNodes =
                    consensusNodeList.stream().map(String::trim).collect(Collectors.toSet());
        }

        if (map.containsKey("connection")) {
            Map<String, Object> connection = (Map<String, Object>) map.get("connection");
            this.nodeUrl = (String) connection.get("nodeUrl");
        }

        if (map.containsKey("resources")) {
            Map<String, Object> resourceMap = (Map<String, Object>) map.get("resources");
            this.resources =
                    resourceMap
                            .entrySet()
                            .stream()
                            .map(Resource::new)
                            .collect(Collectors.toMap(Resource::getName, Function.identity()));
        }
    }

    public String getChainPath() {
        return this.chainPath != null ? this.chainPath : this.defaultChainPath;
    }

    public String getRegionId() {
        return this.regionId;
    }

    public Set<String> getConsensusNodes() {
        return this.consensusNodes;
    }

    public String getNodeUrl() {
        return this.nodeUrl;
    }

    public Map<String, Resource> getResources() {
        return this.resources;
    }

    @Override
    public String toString() {
        return "Config { "
                + "chainPath='"
                + this.chainPath
                + "', "
                + "regionId='"
                + this.regionId
                + "', "
                + "consensusNodes=[ "
                + String.join(", ", this.consensusNodes)
                + " ], "
                + "nodeUrl='"
                + this.nodeUrl
                + "'"
                + " }";
    }

    @SuppressWarnings("unchecked")
    public static class Resource {

        private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

        private final String name;
        private final String address;
        private final String abi;

        private AbiDefinition[] abiDefinitions;

        private Resource(Map.Entry<String, Object> entry) {
            this.name = entry.getKey();

            Map<String, Object> map = (Map<String, Object>) entry.getValue();
            this.address = (String) map.get("address");
            this.abi = (String) map.get("abi");
            try {
                this.abiDefinitions = OBJECT_MAPPER.readValue(abi, AbiDefinition[].class);
            } catch (JsonProcessingException e) {
                // Invalid ABI
            }
        }

        public String getName() {
            return this.name;
        }

        public String getAddress() {
            return this.address;
        }

        public String getAbi() {
            return this.abi;
        }

        public AbiDefinition[] getAbiDefinitions() {
            return this.abiDefinitions;
        }

        public String[] getMethods() {
            if (this.abiDefinitions == null) {
                return null;
            }

            return Arrays.stream(this.abiDefinitions)
                    .filter(abiDefinition -> abiDefinition.getType().equals("function"))
                    .map(AbiDefinition::getName)
                    .toArray(String[]::new);
        }

        @Override
        public String toString() {
            return "Resource { " + "name='" + this.name + "'" + " }";
        }
    }
}
